На главную > Блог > Категория > 🦀 Бэктестинг стратегий на Rust: от идеи до продакшн-системы
Вы когда-нибудь запускали бэктест на Python и уходили пить кофе, потому что ждать приходилось по 10 минут? А может, вы пробовали оптимизировать стратегию с помощью GridSearch и поняли, что на перебор 1000 комбинаций параметров уйдёт целый день? Это не ваша вина. Это ограничения языка.
Rust — язык, который работает на скорости C++ с безопасностью памяти, недостижимой для большинства других языков. В контексте бэктестинга это означает: миллионы свечей обрабатываются за секунды, а не минуты; большой объём данных перебирается с нулевыми затратами на сборку мусора; алгоритмы линейной сложности действительно остаются линейными на практике.
В этой статье я расскажу о лучших инструментах для бэктестинга на Rust, покажу примеры кода из реальных проектов и объясню, почему Rust — это выбор профессионалов, когда речь идёт о серёзных объёмах данных и высокочастотной торговле.
«Python — для прототипов и исследований. Rust — для продакшена и бэктестов на терабайтах данных. Скорость решает всё, когда каждый день на счету».
Бэктестинг — это вычислительно интенсивная задача. Чем больше исторических данных вы обрабатываете, чем сложнее ваша стратегия, тем дольше вы ждёте результат. Rust решает эту проблему на уровне языка.
Для трейдера это означает:
| Проект | Что делает | Ключевая особенность | Для кого |
|---|---|---|---|
| Kronos | Бэктест-фреймворк с WebAssembly-стратегиями | Поддержка WebAssembly — стратегии на любом языке, наносекундная точность [citation:3] | HFT-трейдеры, кросс-языковые команды |
| RaptorBT | Drop-in замена для VectorBT с PyO3-биндингами | Полная совместимость с Python, ускорение до 5800x, 33 метрики [citation:5] | Quant-разработчики, переходящие с Python |
| Nanobook | Исполнение Python-стратегий через Rust-ядро | Детерминированное исполнение LOB (6 млн оп/сек), интеграция с IBKR/Binance [citation:4] | От исследования к продакшену, bridge между Python и Rust |
| QuantForge | CLI-инструмент с детерминированным движком | Decimal-арифметика, валидация данных, SQLite-хранение [citation:7] | CLI-first трейдеры, ценящие воспроизводимость |
| Rustrade | Event-driven экосистема для live- и бэктестинга | Многопоточная архитектура на Tokio, поддержка Binance/IBKR/Hyperliquid [citation:10] | Профессиональная live-торговля с бэктестами |
RaptorBT — идеальный выбор для тех, кто хочет получить скорость Rust, не переписывая весь код на новый язык. Это drop-in замена для VectorBT с полностью совместимыми метриками [citation:5].
import numpy as np
import pandas as pd
import raptorbt
# 1. Подготовка данных (тот же Pandas DataFrame, что и в VectorBT)
df = pd.read_csv("btc_data.csv", index_col=0, parse_dates=True)
# 2. Генерация сигналов (SMA crossover)
sma_fast = df['close'].rolling(10).mean()
sma_slow = df['close'].rolling(20).mean()
entries = (sma_fast > sma_slow) & (sma_fast.shift(1) <= sma_slow.shift(1))
exits = (sma_fast < sma_slow) & (sma_fast.shift(1) >= sma_slow.shift(1))
# 3. Настройка бэктеста
config = raptorbt.PyBacktestConfig(
initial_capital=100000, # начальный капитал
fees=0.001, # 0.1% комиссия
slippage=0.0005, # 0.05% проскальзывание
upon_bar_close=True
)
# 4. Добавление стоп-лоссов и тейк-профитов
config.set_fixed_stop(0.02) # 2% stop-loss
config.set_fixed_target(0.04) # 4% take-profit
# 5. Запуск бэктеста (вся тяжелая работа — в Rust-ядре)
result = raptorbt.run_single_backtest(
timestamps=df.index.astype('int64').values,
open=df['open'].values,
high=df['high'].values,
low=df['low'].values,
close=df['close'].values,
volume=df['volume'].values,
entries=entries.values,
exits=exits.values,
direction=1, # 1 = Long, -1 = Short
weight=1.0,
symbol="BTC",
config=config,
)
# 6. Анализ результатов
print(f"Total Return: {result.metrics.total_return_pct:.2f}%")
print(f"Sharpe Ratio: {result.metrics.sharpe_ratio:.2f}")
print(f"Max Drawdown: {result.metrics.max_drawdown_pct:.2f}%")
print(f"Win Rate: {result.metrics.win_rate_pct:.2f}%")
print(f"Total Trades: {result.metrics.total_trades}")
# 7. Получение кривой капитала и трейдов
equity = result.equity_curve()
trades = result.trades()
Большинство профессиональных бэктестеров используют событийно-ориентированную архитектуру. Она позволяет точно симулировать исполнение ордеров, проскальзывание и управление капиталом [citation:1].
Пример базового бэктестера из проекта rust-learning-for-trading [citation:1]:
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct Bar {
pub time: DateTime<Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
#[derive(Debug, Clone)]
pub enum OrderType {
Market,
Limit(f64),
Stop(f64),
}
#[derive(Debug, Clone)]
pub struct Order {
pub id: u64,
pub symbol: String,
pub side: Side,
pub order_type: OrderType,
pub quantity: f64,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone)]
pub enum Event {
Bar(Bar),
OrderFilled(Order),
OrderPlaced(Order),
TradeOpen(Trade),
TradeClosed(Trade),
PositionUpdated { symbol: String, quantity: f64, avg_price: f64 },
}
pub struct BacktestEngine {
events: Vec<Event>,
portfolio: Portfolio,
strategy: Box<dyn Strategy>,
}
impl BacktestEngine {
pub fn run(&mut self, data: &[Bar]) -> BacktestResult {
for bar in data {
// Генерируем событие новой свечи
self.events.push(Event::Bar(bar.clone()));
// Стратегия генерирует сигналы на основе событий
let signals = self.strategy.on_event(&self.events, &self.portfolio);
// Обрабатываем сигналы в ордера
for signal in signals {
let order = self.create_order(signal, bar);
self.events.push(Event::OrderPlaced(order.clone()));
// Симуляция исполнения (с учётом комиссий и проскальзывания)
let fill_price = self.calculate_fill_price(order.order_type, bar);
self.events.push(Event::OrderFilled(order));
// Обновляем портфель
self.portfolio.update(order, fill_price);
}
}
self.calculate_metrics()
}
}
Одна из главных проблем бэктестинга на Python — недетерминированность. Перемещение индексов в Pandas, нестабильность хешей словарей, порядок итераций по множествам — всё это приводит к тому, что один и тот же бэктест может давать чуть разные результаты при каждом запуске.
Rust решает эту проблему на уровне языка и библиотек [citation:4][citation:5][citation:7]:
Когда ваша стратегия готова, следующий шаг — оптимизация параметров. В Rust это делается просто и элегантно с помощью библиотеки Rayon, которая превращает последовательные итерации в многопоточные [citation:5].
use rayon::prelude::*;
#[derive(Clone)]
struct StrategyParams {
fast_period: usize,
slow_period: usize,
stop_loss_pct: f64,
}
fn backtest_single(params: &StrategyParams, data: &Data) -> f64 {
// Бэктест с заданными параметрами, возвращает итоговую доходность
// ...
}
fn optimize_parameters(data: &Data) -> StrategyParams {
let fast_periods = 5..=20;
let slow_periods = 20..=50;
let stop_losses = [0.01, 0.02, 0.03, 0.05];
// Создаём все комбинации параметров (всего ~15000)
let params: Vec<StrategyParams> = fast_periods.flat_map(|fast|
slow_periods.flat_map(|slow|
stop_losses.iter().map(move |&sl| StrategyParams {
fast_period: fast,
slow_period: slow,
stop_loss_pct: sl,
})
)
).collect();
// Параллельный бэктест всех комбинаций
let results: Vec<(StrategyParams, f64)> = params.par_iter()
.map(|p| (p.clone(), backtest_single(p, data)))
.collect();
// Находим лучшие параметры
results.into_iter()
.max_by(|(_, r1), (_, r2)| r1.partial_cmp(r2).unwrap())
.map(|(p, _)| p)
.unwrap()
}
Не обязательно переписывать всё на Rust. Современный подход — использовать Rust для вычислительного ядра, а Python для высокоуровневой логики, визуализации и анализа.
PyO3 позволяет вызывать Rust-код из Python с минимальными накладными расходами. Все ведущие библиотеки (RaptorBT, Nanobook, Fundcloud) используют этот подход [citation:2][citation:4][citation:5].
# Пример использования Nanobook из Python
import nanobook
# Создаём портфель и запускаем бэктест
result = nanobook.backtest_weights(
weight_schedule=[...], # Python-логика расчёта весов
price_schedule=[...], # исторические данные
initial_cash=1_000_000,
cost_bps=15,
stop_cfg={"trailing_stop_pct": 0.05},
)
# Все расчёты — в Rust, GIL отпущен
print(f"Sharpe: {result['metrics'].sharpe:.2f}")
print(f"Max DD: {result['metrics'].max_drawdown:.1%}")
| Характеристика | Python (VectorBT, Backtrader) | Rust (RaptorBT, Nanobook, Rustrade) |
|---|---|---|
| Скорость (1000 свечей) | ~1460 мс (с JIT-прогревом) | ~0.25 мс |
| Скорость (50000 свечей) | ~43 мс | ~1.7 мс |
| Сборка мусора | Да, непредсказуемые паузы | Нет, детерминированное управление памятью |
| Размер библиотеки (диск) | ~450 MB | <10 MB |
| Параллельные вычисления | Ограничены GIL, сложно | Из коробки (Rayon, Tokio) |
| Детерминизм | Нет (из-за хешей, индексов) | Да (строгие правила исполнения) |
Бэктестинг на Rust — это не хайп, а прагматичный выбор для тех, кто работает с большими объёмами данных, сложными стратегиями или высокочастотной торговлей. Ускорение в десятки и сотни раз, детерминизм, безопасная многопоточность и предсказуемое управление памятью — не просто маркетинговые лозунги, а реальные преимущества, которые конвертируются в более быстрые итерации, более глубокую оптимизацию и, в конечном счёте, в лучшие торговые решения.
Начните с малого: установите RaptorBT, запустите бэктест вашей стратегии на Rust-движке, сравните скорость. Затем попробуйте оптимизировать параметры с Rayon. Потом, если почувствуете вкус, погрузитесь глубже — изучите Kronos, Nanobook или QuantForge. Экосистема Rust для бэктестинга растёт, и сейчас идеальное время, чтобы в неё войти.
И помните: даже самый быстрый бэктестер не спасёт от плохой стратегии. Но он даст вам инструмент, который позволит тестировать идеи быстрее, чем конкуренты. А в трейдинге скорость итераций — это половина успеха.
«Python делает вещи возможными. Rust делает вещи быстрыми. На дистанции побеждает тот, у кого оба инструмента в арсенале».
Дата размещения статьи: 04-06-2026 в 11:16:40